#ifndef SIMPLE_SUPPORT_ITERATOR_BYTE_HPP
#define SIMPLE_SUPPORT_ITERATOR_BYTE_HPP

#include <cstddef> // std::byte
#include <cstring> // std::memcpy
#include <cassert> // assert
#include <type_traits> // std::enable_if_t std::is_trivially_copyable_v
#include <iterator> // std::iterator_traits std::random_access_iterator_tag

namespace simple::support
{

	// TODO: well in c++23 tuple still not trivialy copyable so gotta support it here explicitly i guess
	// basically use tuple interface as plan b to trivially copyable, if elemnts are trivially copyable
	// could also allow opting into it even for trivially copyable's, as a well defined serialization abstraction

	template <typename T,
		std::enable_if_t<std::is_trivially_copyable_v<T>>* = nullptr>
	void byte_copy(const T& value, std::byte* raw)
	{
		const auto byte_view = reinterpret_cast<const std::byte*>(&value);
		std::memcpy(raw, byte_view, sizeof(T));
	}

	template <typename T, std::enable_if_t<std::is_trivially_copyable_v<T>>* = nullptr>
	void byte_copy(const std::byte* raw, T& value)
	{
		const auto byte_view = reinterpret_cast<std::byte*>(&value);
		std::memcpy(byte_view, raw, sizeof(T));
	}

	template <typename T, std::enable_if_t<std::is_trivially_copyable_v<T>>* = nullptr>
	T byte_copy(const std::byte* raw)
	{
		T value{};
		const auto byte_view = reinterpret_cast<std::byte*>(&value);
		std::memcpy(byte_view, raw, sizeof(T));
		return value;
	}

	template <typename T, std::enable_if_t<std::is_trivially_copyable_v<T>>* = nullptr>
	class const_indirect_value
	{
		public:
		const_indirect_value(const std::byte* raw) : value(byte_copy<T>(raw)) {};
		T* operator->() { return &value; }

		private:
		T value;
	};

	template <typename T, std::enable_if_t<std::is_trivially_copyable_v<T>>* = nullptr>
	class indirect_value
	{
		public:
		indirect_value(std::byte* raw) : raw(raw), value(byte_copy<T>(raw)) {}
		~indirect_value() { byte_copy(value, raw); }
		T* operator->() { return &value; }

		private:
		std::byte* raw;
		T value;
	};

	enum class bitcast_style
	{
		copy, // universal, simple, value semantics only
		reinterpret, // allaows reference semantics, but requires careful setup TODO:
	};

	template <typename T, bitcast_style bcs = bitcast_style::copy,
		std::enable_if_t<std::is_trivially_copyable_v<T>>* = nullptr>
	struct byte_iterator
	{
		std::byte* raw;

		using value_type = T;
		using difference_type = std::iterator_traits<std::byte*>::difference_type;
		using pointer = void;
		using reference = void;
		using iterator_category = std::random_access_iterator_tag;

		private:

		class proxy
		{
			public:
			proxy(const proxy& proxy) = delete;
			proxy(const proxy&& proxy) = delete;
			proxy& operator=(const proxy&) = delete;
			proxy& operator=(proxy&&) & = delete;
			proxy& operator=(proxy&&) const& = delete;
			proxy& operator=(proxy&&) && = delete;
			proxy& operator=(proxy&&) const&& = delete;

			// a value with reference semantics is not a reference,
			// we are not going to pretend that we can get a reference when we can't
			// this is a short lived object to allow read/write upon dereference
			// you are not allowed to hold on to this object for read/write
			// unless you use move semantics, for example to perfectly forward.
			// basically want proxy to alsways be an rvalue
			// so try to make it unusable as lvalue
			// there is only one erroneous case that will pass silently:
			// auto x = *it; // x is reference not a copy
			// ... change underlying value ...
			// value_type z = std::move(x); // converts to a current value instead of expected copy
			// value_type zz = x; // this doesn't work, lvalue can't convert to value_type
			// *it = x; // this also doesn't work cause of delted operator=
			// *it = std::move(x); // as well as this
			// just that one case couldn't cover :/
			const proxy& operator=(const value_type& v) const &&
			{ byte_copy(v, raw);  return *this; }
			[[nodiscard]]
			operator value_type() const &&
			{ value_type v{}; byte_copy(raw,v); return v; }

			private:
			proxy(std::byte* raw) : raw(raw) {};
			std::byte* raw;

			friend struct byte_iterator;
		};

		public:

		const byte_iterator& operator++() { raw += sizeof(T); return *this; };
		const byte_iterator& operator++(int) { auto prev = *this; operator++(); return prev; };
		const byte_iterator& operator--() { raw -= sizeof(T); return *this; };
		const byte_iterator& operator--(int) { auto prev = *this; operator--(); return prev; };

		[[nodiscard]] friend byte_iterator operator+(byte_iterator x, difference_type d)
		{ x.raw += d * sizeof(T); return x; }
		[[nodiscard]] friend byte_iterator operator+(difference_type d, byte_iterator x)
		{ return x + d; }
		[[nodiscard]] friend byte_iterator operator-(byte_iterator x, difference_type d)
		{ x.raw -= d * sizeof(T); return x; }
		[[nodiscard]] friend difference_type operator-(const byte_iterator& x, const byte_iterator& y)
		{
			assert((x.raw-y.raw) % sizeof(T) == 0);
			return (x.raw - y.raw) / sizeof(T);
		}

		byte_iterator& operator+=(difference_type d)
		{ raw += d * sizeof(T); return *this; }
		byte_iterator& operator-=(difference_type d)
		{ raw -= d * sizeof(T); return *this; }

		[[nodiscard]] bool operator==(const byte_iterator& other)
		{
			assert((raw - other.raw) % sizeof(T) == 0);
			return raw == other.raw;
		}
		[[nodiscard]] bool operator!=(const byte_iterator& other)
		{ return not ((*this) == other); }

		[[nodiscard]] bool operator<(const byte_iterator& other)
		{
			assert((raw - other.raw) % sizeof(T) == 0);
			return raw < other.raw;
		}
		[[nodiscard]] bool operator>=(const byte_iterator& other)
		{ return not ((*this) < other); }

		[[nodiscard]] bool operator>(const byte_iterator& other)
		{
			assert((raw - other.raw) % sizeof(T) == 0);
			return raw > other.raw;
		}
		[[nodiscard]] bool operator<=(const byte_iterator& other)
		{ return not ((*this) > other); }


		const proxy operator*() const { return {raw}; };

		// TODO: if T is const
		// const const_proxy operator*() const { return raw; };

		indirect_value<value_type> operator->() const
		{ return raw; };

		// TODO: if T is const
		// const_indirect_value<value_type> operator->() const
		// { return raw; };

	};

} // namespace simple::support

#endif /* end of include guard */
